package gatperdut.chato.core;

import gatperdut.chato.core.cdo.CDODatasource;
import gatperdut.chato.core.characters.BaseController;
import gatperdut.chato.core.characters.BotController;
import gatperdut.chato.core.craft.CraftController;
import gatperdut.chato.core.craft.CraftStepController;
import gatperdut.chato.core.entities.PlayerController;
import gatperdut.chato.core.handler.craft.CraftHandler;
import gatperdut.chato.core.handler.entity.EntityHandler;
import gatperdut.chato.core.handler.item.ItemHandler;
import gatperdut.chato.core.handler.item.template.ItemTemplateHandler;
import gatperdut.chato.core.handler.room.RoomHandler;
import gatperdut.chato.core.messaging.RPCServer;
import gatperdut.chato.core.threads.ThreadCompleteListener;
import gatperdut.chato.model.Chato.Craft.Craft;
import gatperdut.chato.model.Chato.Craft.CraftStep;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.log4j.Logger;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ChatoServer implements ThreadCompleteListener {

    private ApplicationContext ctx = new ClassPathXmlApplicationContext( "cdo-client-config.xml" );

    public static void main( String[] args ) {
        ChatoServer cs = new ChatoServer();

        cs.setup();

        cs.serve();
    }

    private final Logger logger = Logger.getLogger( ChatoServer.class );

    private CDODatasource datasource;

    private ServerSocket serverSocket;

    private EntityHandler entityHandler;

    private RoomHandler roomHandler;

    private ItemHandler itemHandler;

    private ItemTemplateHandler itemTemplateHandler;

    private CraftHandler craftHandler;

    private RPCServer rPCServer;

    private boolean shuttingDown = false;

    @Option( name = "-port", usage = "Port to listen on. Default is 1182." )
    private int port = 1182;

    @Option( name = "-mncr", usage = "Max. number of simulatenous client requests. Default is 5." )
    private int mncr = 5;

    private void parseArguments( String... appArgs ) {
        CmdLineParser parser = new CmdLineParser( this );

        try {
            parser.parseArgument( appArgs );
        }
        catch ( CmdLineException e ) {
            getLogger().error( "Incorrect parameters: " + e.getMessage() );
            parser.printUsage( System.err );
        }
    }

    private void setupStorage() {

        setDatasource( (CDODatasource) ctx.getBean( "cdoDatasource" ) );

        getDatasource().init();
    }

    private void setupServerSocket() {
        try {
            setServerSocket( new ServerSocket( getPort(), getMncr() ) );
        }
        catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    public ChatoServer() {

        getLogger().info( "Chato Server started." );

    }

    public void setup( String... appArgs ) {

        parseArguments( appArgs );

        setupStorage();

        setupServerSocket();

        loadHandlers();

        loadMessaging();

        getRoomHandler().loadRooms();

        loadCrafts();

        getEntityHandler().loadBots();
    }

    private Socket listen() throws IOException {
        Socket client = null;

        client = getServerSocket().accept();
        return client;
    }

    private void loadHandlers() {
        entityHandler = new EntityHandler( this );
        itemHandler = new ItemHandler( this );
        roomHandler = new RoomHandler( this );
        craftHandler = new CraftHandler( this );
        setItemTemplateHandler( new ItemTemplateHandler( this, getDatasource().getChato().getItemTemplateContainer() ) );
    }

    private void loadMessaging() {
        rPCServer = new RPCServer( this );
        rPCServer.start();
    }

    private void loadCrafts() {
        CraftController cc;
        for ( Craft craft : getDatasource().getChato().getCraftContainer().getCrafts() ) {
            cc = new CraftController( craft );
            getCraftHandler().getCraftToController().put( craft, cc );
            CraftStepController csc;
            for ( CraftStep cs : craft.getSteps() ) {
                csc = new CraftStepController( cs );
                cc.getCraftStepToController().put( cs, csc );
            }
        }
    }

    public void serve() {
        Socket newClientSocket;
        BaseController playerController = null;

        getLogger().info( "Chato Server: listening now on port " + getPort() + "..." );

        while ( true ) {
            try {
                newClientSocket = listen();
            }
            catch ( IOException e ) {
                if ( isShuttingDown() ) {
                    getLogger().info( "Shutting down ..." );

                }
                else {
                    getLogger().info( "Shutdown requested ..." );
                }
                for ( PlayerController pc : getEntityHandler().getSocketToController().values() ) {
                    pc.tx( "Shutting down server ..." );
                    try {
                        pc.getSocket().close();
                        pc.interrupt();
                    }
                    catch ( IOException e1 ) {

                    }
                }
                for ( BotController bc : getEntityHandler().getNpcToController().values() ) {
                    bc.interrupt();
                }
                rPCServer.interrupt();
                break;
            }
            if ( newClientSocket == null ) {
                getLogger().error( "Something went wrong with an incoming connection." );
                break;
            }

            playerController = getEntityHandler().createPlayerController( newClientSocket );

            getLogger().info(
                    "New connection from " + newClientSocket.getInetAddress() + ":" + newClientSocket.getPort() + ". "
                            + getEntityHandler().getConnections().size() + " connections currently." );

            ( (BaseController) playerController ).start();
        }

        // --attempt a save before shuttin down.
        getDatasource().save();
        getDatasource().shutdown();
    }

    public void shutdown() {
        try {
            setShuttingDown( true );
            getServerSocket().close();
        }
        catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    public void notifyOfThreadComplete( Thread thread ) {
        Socket socket = ( (PlayerController) thread ).getSocket();

        getEntityHandler().removeConnection( socket );

        getEntityHandler().logOutPlayer( ( (PlayerController) thread ).getPlayer() );

        getLogger().info( ( (PlayerController) thread ).whoAmI() + ". " + getEntityHandler().getConnections().size() + " connections currently." );

    }

    public Logger getLogger() {
        return logger;
    }

    public CDODatasource getDatasource() {
        return datasource;
    }

    public void setDatasource( CDODatasource datasource ) {
        this.datasource = datasource;
        ;
    }

    public ServerSocket getServerSocket() {
        return serverSocket;
    }

    public void setServerSocket( ServerSocket serverSocket ) {
        this.serverSocket = serverSocket;
    }

    public int getPort() {
        return port;
    }

    public void setPort( int port ) {
        this.port = port;
    }

    public int getMncr() {
        return mncr;
    }

    public void setMncr( int mncr ) {
        this.mncr = mncr;
    }

    public boolean isShuttingDown() {
        return shuttingDown;
    }

    public void setShuttingDown( boolean shuttingDown ) {
        this.shuttingDown = shuttingDown;
    }

    public EntityHandler getEntityHandler() {
        return entityHandler;
    }

    public ItemHandler getItemHandler() {
        return itemHandler;
    }

    public RoomHandler getRoomHandler() {
        return roomHandler;
    }

    public CraftHandler getCraftHandler() {
        return craftHandler;
    }

    public ItemTemplateHandler getItemTemplateHandler() {
        return itemTemplateHandler;
    }

    public RPCServer getRPCServer() {
        return rPCServer;
    }

    public void setItemTemplateHandler( ItemTemplateHandler itemTemplateHandler ) {
        this.itemTemplateHandler = itemTemplateHandler;
    }

}
